import { v3, Vec3 } from "cc";
import { DataProxy } from "../../frame/A-LIB/lib.b.data";
import GlobalDC from "../../frame/common/data.global.center";
import { APP_NAME } from "../../frame/frame.config";
import { NCPoint, NCPointData, NCUserPoint } from "../api/api.config.client";
import { ServiceAPIURI, ServiceBody, ServiceResult } from "../api/api.config.service";
import { ClientAPI } from "../api/client.api";
import ServiceAPI from "../api/service.api";
import Config from "./config.manager";
import GameDC from "./data.game.center";
import GameAssets from "./game.assets";

export default class GameManager {
	public static get AppName(){
		return APP_NAME;
	}

	private static _init_np_: mtec.NudityPromise<'end'>;
	public static get inited(){
		return GameManager._init_np_.promise;
	}

	public static async init(){
		GameManager._init_np_ = new mtec.NudityPromise();
		GlobalDC.init();
		GameDC.init();
		//await Config.init(country);

		let country: string;
		await Promise.all([
			ClientAPI.Local(),
			ClientAPI.DeviceID()
		]).then(([local, device_id])=>{
			country = Config.safeCountry(local.countryCode);
			GameDC.Status.deviceID = device_id;
		});

		country = await GameManager.initUserInfo(country);

		await Promise.all([
			ServiceAPI.countConf(country),
			ServiceAPI.otherConf(country)])
		.then(([count, other])=>{
			Config.setCountryConfig(count);
			Config.setOfficialConfig(other);
		});
		await GameAssets.init(Config.country, Config.language);

		GameManager.listening();

		GameManager._init_np_.resolve('end');
	}

	private static async initUserInfo(country: string){
		if(!GameDC.User.uid){
			let result = await ServiceAPI.userInit(GameDC.Status.deviceID, country);
			GameDC.User.uid = result.userId;
		}

		return ServiceAPI.userInfo(GameDC.User.uid).then(res=>{
			GameDC.User.symbol = res.symbol;
			GameDC.User.diamond = res.diamond;
			GameManager.setFreePool(res.freePool);

			return res.country;
		});
	}


	private static listening(){
		if(GlobalDC.Trigger.NEW_DAY){
			this.newDay();
		}else{
			this.checkEmpty();
		}

		DataProxy.follow(GlobalDC.Trigger, 'NEW_DAY', (_, _new_)=>{
			if(_new_){
				GameManager.newDay();
			}else{
				GameManager.checkEmpty();
			}
		}, false);

		DataProxy.follow(GameDC.User, 'round', (_, round)=>{
			GameManager.refreshStatistics(Date.now());

			GameManager.fusionGet(0);
			GameManager.fusionGet(1);
		});

		GlobalDC.interval(60 * 1000, (td, now)=>GameManager.refreshStatistics(now));
	}

	private static initRankList(){
		GameDC.initRankData(GameAssets.Nickname, Config.officialConf.rank);
	}

	/**
	 * 刷新统计信息
	 * @param now
	 */
	private static refreshStatistics(now: number){
		let pass_time = (now - new Date(now).setHours(0, 0, 0, 0)) / 1000 / 60;
		let round = GameDC.User.round;

		GameDC.Status.statistics_count = Math.round(pass_time * (round<3 ? 20 : 23)/** 每分钟的人数增长（与关卡相关的配置） */);
		GameDC.Status.statistics_round = round>=4 ? mtec.number.random(20, 21, 2) : 1;
		GameDC.Status.statistics_free = round<4 ? Reflect.get(Config.officialConf.statistics, 'free'+round+'Total') * GameDC.Status.statistics_round : mtec.number.random(...Config.officialConf.statistics.free4TotalRange, 2);
	}

	private static newDay(){
		GameManager.initRankList();
	}

	private static checkEmpty(){
		let rank_list = mtec.local.read(APP_NAME + 'rank_list');
		if(!rank_list){
			GameManager.initRankList();
		}
	}

	private static fusion_fruit: Promise<mtec.Fusion<{type:0|1}, Awaited<ReturnType<typeof ServiceAPI.fruitGet>>>>[] = [];
	private static __fusion_map__: Map<number, Awaited<typeof GameManager.fusion_fruit[number]>> = new Map();
	/**
	 * 现金获取类型
	 * [0]: 普通消除;
	 * [1]: 特殊消除;
	 */
	public static async fusionGet(type: 0|1){
		let fruit: typeof GameManager.fusion_fruit[number];

		fruit = GameManager.fusion_fruit[type];

		GameManager.fusion_fruit[type] = ServiceAPI.fruitGet(GameDC.User.uid, type, GameDC.User.round)
		.then(res=>{
			let record = {...res,
				type,
			};
			GameManager.__fusion_map__.set(record.logId, record);
			return record;
		});

		return await fruit;
	}

	public static get fusionConfirmDone(){
		return Promise.all(GameManager.__fusion_list__.map(el=>el.np.promise));
	}
	public static fusionConfirm(logId: number){
		let np = new mtec.NudityPromise<any>();
		let record = GameManager.__fusion_map__.get(logId);
		GameDC.User.symbol += record.amount;
		if(record.diamond){
			GameDC.User.diamond += record.diamond;
		}
		GameManager.runFusionConfirm({
			logId, np,
			symbol: GameDC.User.symbol,
			diamond: GameDC.User.diamond
		 });
	}

	private static __fusion_list__: {logId: number, symbol: number, diamond: number, np: mtec.NudityPromise<Awaited<ReturnType<typeof ServiceAPI.fruitConfirm>>>}[] = [];
	private static __fusion_confirm_run_lock__ = false;
	private static __fusion_confirm_idx__ = 0;
	private static async runFusionConfirm(_task_?: typeof GameManager.__fusion_list__[number]){
		if(_task_){
			GameManager.__fusion_list__.push(_task_);
		}
		if(GameManager.__fusion_confirm_run_lock__){
			return void 0;
		}
		GameManager.__fusion_confirm_run_lock__ = true;

		let uid = GameDC.User.uid;
		let task: typeof GameManager.__fusion_list__[number];
		let record: mtec.MapValue<typeof GameManager.__fusion_map__>;
		let res: Awaited<ReturnType<typeof ServiceAPI.fruitConfirm>>;
		while(GameManager.__fusion_list__.length>GameManager.__fusion_confirm_idx__){
			task = GameManager.__fusion_list__[GameManager.__fusion_confirm_idx__];
			record = GameManager.__fusion_map__.get(task.logId);
			res = await ServiceAPI.fruitConfirm(uid, task.logId, record.type);
			GameManager.__fusion_map__.delete(record.type);
			if(record.type==0){
				GameManager.userPoint('UserGetFruit');
			}
			task.np.resolve(res);
			GameManager.__fusion_confirm_idx__++;
		}


		if(GameManager.__fusion_list__.length>GameManager.__fusion_confirm_idx__){
			GameManager.__fusion_confirm_run_lock__ = false;
			GameManager.runFusionConfirm();
		}else{
			GameDC.User.symbol = res.symbol;
			if(GameDC.User.diamond != task.diamond){
				GameDC.User.diamond = task.diamond;
			}
			GameManager.__fusion_confirm_run_lock__ = false;
		}
	}

	public static setFreePool(freePool: ServiceResult<ServiceAPIURI.UserInfo>['freePool']){
		let old_list = mtec.array.clear(GameDC.FreePool);

		GameDC.FreePool.push(...freePool);

		if(freePool.length != old_list.length || !freePool.every(el=>old_list.find(o=>o.freeID==el.freeID))){
			GameDC.Trigger.REFRESH_FREE_CACHE = true;
		}
	}
	public static async addFreePool(round: number){
		if(!mtec.array.includes(GameDC.FreePool, el=>el.round == round)){
			await ServiceAPI.symbolClean(GameDC.User.uid, true, round);

			let info = await ServiceAPI.userInfo(GameDC.User.uid);

			GameDC.User.symbol = info.symbol;
			GameManager.setFreePool(info.freePool);
		}else{
			let res = await ServiceAPI.symbolClean(GameDC.User.uid, false, round);
			GameDC.User.symbol = res.symbol;
		}

		return GameDC.FreePool.find(el=>el.round == round);
	}
	public static async sendFree(cache: typeof GameDC.FreePool[number], info: mtec.OmitO<ServiceBody<ServiceAPIURI.Free>, 'userId'|'freeID'>){
		await ServiceAPI.Free(GameDC.User.uid, cache.freeID, info.account, info.accountType, info.accountName, info.channel)
		.then(res=>{
			if(res){
				GameManager.userPoint('FruitFree', {fruitLevel: cache.round});
			}
		});
		GameDC.User.last_free_timestamp = Date.now();

		let user_info = await ServiceAPI.userInfo(GameDC.User.uid);
		GameDC.User.symbol = user_info.symbol;
		GameManager.setFreePool(user_info.freePool);
	}

	private static __diamond_change_list__: number[] = [];
	private static __diamond_change_lock__ = false;
	public static changeDiamond(amount: number){
		GameDC.User.diamond += amount;
		GameManager.__diamond_change_list__.push(amount);
		GameManager.runDiamondChange();
	}
	private static async runDiamondChange(){
		if(GameManager.__diamond_change_lock__){
			return void 0;
		}
		GameManager.__diamond_change_lock__ = true;

		let uid = GameDC.User.uid;
		let change_value: number;
		let res: Awaited<ReturnType<typeof ServiceAPI.diamondSync>>;
		while(GameManager.__diamond_change_list__.length>0){
			change_value = GameManager.__diamond_change_list__.pop();
			res = await ServiceAPI.diamondSync(uid, change_value);
			if(!res){
				GameManager.__diamond_change_list__.push(change_value);
			}
		}

		if(GameManager.__diamond_change_list__.length>0){
			GameManager.__diamond_change_lock__ = false;
			GameManager.runDiamondChange();
		}else{
			if(GameDC.User.diamond != res.diamond){
				GameDC.User.diamond = res.diamond;
			}
			GameManager.__diamond_change_lock__ = false;
		}
	}

	public static cleanGameStatus(){
		let round = GameDC.User.round;
		mtec.array.clear(GameDC.prop_geted);
		mtec.array.clear(GameManager.__fusion_list__);
		GameManager.__fusion_confirm_idx__ = 0;
		GameDC.Status.removeTileCount = 0;
		GameDC.Status.gameover = undefined;
		GameDC.Status.resurgence = 1;
		GameDC.Status.prop_stash = 0;
		GameDC.Status.prop_revoke = 0;
		GameDC.Status.prop_shuffle = 0;
	}

	private static __reap_symbol_vec__ = v3();
	private static __reap_symbol_vec_ref__: Vec3;
	public static get reapSymbolVec(){
		return this.__reap_symbol_vec_ref__;
	}
	public static reapSymbol(num: number, world_position: Vec3){
		if(world_position){
			GameManager.__reap_symbol_vec__.set(world_position);
			GameManager.__reap_symbol_vec_ref__ = GameManager.__reap_symbol_vec__;
		}else{
			GameManager.__reap_symbol_vec_ref__ = null;
		}
		GameDC.Trigger.CHANGE_SYMBOL = num;
	}

	private static __reap_diamond_vec__ = v3();
	private static __reap_diamond_vec_ref__: Vec3;
	public static get reapDiamondVec(){
		return this.__reap_diamond_vec_ref__;
	}
	public static reapDiamond(num: number, world_position: Vec3){
		if(world_position){
			GameManager.__reap_diamond_vec__.set(world_position);
			GameManager.__reap_diamond_vec_ref__ = GameManager.__reap_diamond_vec__;
		}else{
			GameManager.__reap_diamond_vec_ref__ = null;
		}
		GameDC.Trigger.CHANGE_DIAMOND = num;
	}

	/**
	 * 用户点位上报
	 * @param point
	 * @param args
	 */
	public static userPoint< p extends NCUserPoint, d extends Omit<NCPointData<typeof NCPoint[p]>, 'userId'> >(point: p, ...args: {} extends d ? [] : [d]){
		let data = args[0] ?? {};
		Reflect.set(data, 'userId', GameDC.User.uid);
		ClientAPI.Point(NCPoint[point], data);
	}
}